package com.wgo.precise.client.ui.model;

import static com.wgo.bpot.common.transport.util.MethodNamingConvention.ADD_METHOD;
import static com.wgo.bpot.common.transport.util.MethodNamingConvention.GET_METHOD;
import static com.wgo.bpot.common.transport.util.MethodNamingConvention.REMOVE_METHOD;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IStatus;

import com.wgo.bpot.common.transport.util.MethodNamingConvention;
import com.wgo.precise.client.ui.controller.ModelStatus;
import com.wgo.precise.client.ui.controller.RequirementPlugin;
import com.wgo.precise.client.ui.controller.exceptions.RematoClientException;
import com.wgo.precise.client.ui.model.facade.IMenuItemsProvider;
import com.wgo.precise.client.ui.model.facade.IModelPersistable;
import com.wgo.precise.client.ui.model.facade.INodeChild;
import com.wgo.precise.client.ui.model.facade.INodeParent;
import com.wgo.precise.client.ui.view.projecttree.DeleteAction;
import com.wgo.precise.client.ui.view.util.ReflectionHelper;
import com.wgo.precise.client.ui.view.util.ViewerAction;
import com.wgo.precise.common.VO.Category;
import com.wgo.precise.common.VO.Nameable;
import com.wgo.precise.common.VO.Persistent;

/**
 * 
 * @author Petter L. H. Eide
 * @version $Id: NodeChild.java,v 1.6 2006-02-19 16:29:37 petterei Exp $
 */
public abstract class NodeChild<T extends INodeParent, S extends Nameable> extends NameableWrapper<S> implements INodeChild<T>, Comparable<NodeChild>, IMenuItemsProvider, IModelPersistable {

	private Method getParentMethod = null;
//	private Method setParentMethod = null;
	private Method addThisAsChild = null;
	private Method removeThisAsChild = null;
	private T parent = null; 
	
    /**
     * Use when created from gui, then set parent.
     */
    protected NodeChild(S nameable, Class<? extends Persistent> parentInterface, String parentCoreMethodName) {
        super(nameable);
        if (null != parentCoreMethodName) {
        	getParentMethod = ReflectionHelper.retrieveMethod("Could not retrieve method: " + GET_METHOD.getPrefix() + parentCoreMethodName, nameable.getClass(),GET_METHOD.getPrefix() + parentCoreMethodName );
        }
//        Class parentType = getParentMethod.getReturnType();
//        setParentMethod = ReflectionHelper.retrieveMethod("Could not retrieve method: " + SET_METHOD.getPrefix() + parentCoreMethodName, nameable.getClass(),SET_METHOD.getPrefix() + parentCoreMethodName , parentType);
        Class<S> thisType = getDomainObjectType();
        addThisAsChild = MethodNamingConvention.findMethod(ADD_METHOD.getPrefix(), thisType.getSimpleName(), parentInterface, thisType);
        if (null == addThisAsChild){
        	addThisAsChild = MethodNamingConvention.findMethod(ADD_METHOD.getPrefix(), "Sub"+thisType.getSimpleName(), parentInterface, thisType);
        }
        removeThisAsChild = MethodNamingConvention.findMethod(REMOVE_METHOD.getPrefix(), thisType.getSimpleName(), parentInterface, thisType);
        initPropertiesFromDomainObject(Configuration.getInstance().getNodeSpesifications().values());
    }
	/**
	 * Use when created from server.
	 */
    protected NodeChild(S nameable, T parent, Class<? extends Persistent> parentInterface, String parentCoreMethodName){
    	this(nameable, parentInterface, parentCoreMethodName);
    	this.parent = parent;
    	init(nameable);
    }
    /**
     * Initialisation after parent is set. 
     */
    protected abstract void init(Nameable nameable);
    
    protected List<AbstractPropertySpesification> getUserDefinedTypes() {
    	if (getParent() instanceof CategoryWrapper) {
    		List<AbstractPropertySpesification> specs = getUserDefinedTypes();
    		specs.addAll(((CategoryWrapper)getParent()).getUserDefinedTypes());
    		return specs;
    	}
    	throw new RematoClientException("Could not find " + Category.class.getSimpleName() + " for requirement");
    }

    public void setParent(T newParent) {
    	if ( getParent() != null ) {
    		if ( getParent().equals(newParent)) {
    			return;
    		}
    		if (null == newParent) {
    	        removeThisAsChild = MethodNamingConvention.findMethod(REMOVE_METHOD.getPrefix(), getDomainModelObject().getClass().getSimpleName(), newParent.getDomainModelObject().getClass(), getDomainModelObject().getClass());
    			if (null == removeThisAsChild){
    				removeThisAsChild = MethodNamingConvention.findMethod(REMOVE_METHOD.getPrefix(), "Sub"+getDomainModelObject().getClass().getSimpleName(), newParent.getDomainModelObject().getClass(), getDomainModelObject().getClass());
    			}
        		ReflectionHelper.invoke("Could not remove child from old parent", getParent().getDomainModelObject(), removeThisAsChild, getDomainModelObject());
    		}
    	}
    	if (null != newParent) {
            addThisAsChild = MethodNamingConvention.findMethod(ADD_METHOD.getPrefix(), getDomainModelObject().getClass().getSimpleName(), newParent.getDomainModelObject().getClass(), getDomainModelObject().getClass());
            if (null == addThisAsChild){
                addThisAsChild = MethodNamingConvention.findMethod(ADD_METHOD.getPrefix(), "Sub"+getDomainModelObject().getClass().getSimpleName(), newParent.getDomainModelObject().getClass(), getDomainModelObject().getClass());
            }
   		ReflectionHelper.invoke("Could not add child to new parent", newParent.getDomainModelObject(), addThisAsChild, getDomainModelObject());
    	}
//    	if (null != setParentMethod ) {
//    		Object argument = null != newParent ? newParent.getDomainModelObject() : null;
//    		ReflectionHelper.invoke("Could not set parent", getDomainModelObject(), setParentMethod, argument);
//    	}
    	parent = newParent;
    }
    
    public final T getParent() {
    	if (null != getParentMethod && null == parent && null != getDomainModelObject()) { //lazy wrapper-creation..
    		Persistent persistentParent = ReflectionHelper.<Persistent>invoke("Could not get parent.", getDomainModelObject(), getParentMethod);
    		parent =  null != persistentParent ? RequirementPlugin.getInstance().getSession().getWrapperFactory().<T>getWrapper(persistentParent) : null ;
    	}
    	return parent;
    }

    public List<ViewerAction> getMenuItemActions() {
        List<ViewerAction> actionList= new ArrayList<ViewerAction>();
        actionList.add(new DeleteAction(this));
        return actionList;
    }

	/**
	 * @see java.lang.Comparable#compareTo(Object)
	 */
	public int compareTo(NodeChild otherNodeChild) { // TODO use getSortingOrder??
		int i = 0;
		if (otherNodeChild != null) {
			AbstractPropertySpesification instanceNameIdentifier = Configuration.getInstance().getNodeTitleSpesification();

			PropertyValue thisNameValue = getCurrentPropertyValue(instanceNameIdentifier);
			Object thisNameObject = null != thisNameValue ? thisNameValue.getValue() : null;
			// debug:
			if (thisNameObject != null && ! (thisNameObject instanceof String)){
				RequirementPlugin.log(new ModelStatus(IStatus.WARNING, "NameIdentifier error!", null));
			}
			String thisName = (String)thisNameObject;

			PropertyValue otherNameValue = otherNodeChild.getCurrentPropertyValue(instanceNameIdentifier);
			Object otherNameObject = null != otherNameValue ? otherNameValue.getValue() : null;
			// debug:
			if (otherNameObject != null && ! (otherNameObject instanceof String)){
				RequirementPlugin.log(new ModelStatus(IStatus.WARNING, "NameIdentifier error!", null));
			}
			String otherName = (String)otherNameObject;
			if (null != thisName && null != otherName) {
				i = thisName.compareTo(otherName);
			} else if (null != thisName) {
				i = -1;
			} else if (null != otherName) {
				i = 1;
			}
		}
		return i;
	}

}